Storing the State in a Remote Backend

Learn how to create a storage bucket to hold and save the state of our cluster.

Creating a Google storage bucket#

Terraform maintains its internal information about the current state. That allows it to deduce what needs to be done and to converge the actual into the desired state defined in *.tf files. Currently, that state is stored locally in the terraform.tfstate file. For now, there shouldn’t be anything exciting in it. We can see this file in the code playground below by running the cat terraform.tfstate. But for reference, here are the contents of terraform.tfstate.

Output of terraform.tfstate

The field that really matters is resources. It’s empty because we didn’t define any. We’ll do that soon, but we’re not going to create anything related to our GKE cluster. At least not right away. What we need right now is a storage bucket.

Keeping Terraform’s state local is a bad idea. If it’s on a laptop, we can’t allow others to modify the state of our resources. We’d need to send them the terraform.tfstate file through email, keep it on a network drive, or implement some other similar solution. That is impractical.

We might be tempted to store it in Git, but that wouldn’t be secure. Instead, we’ll tell Terraform to keep the state in a Google bucket. Since we’re trying to define infrastructure as code, we won’t do that by executing a shell command, nor will we go to the GCP console. We’ll tell Terraform to create the bucket. It will be the first resource Terraform manages.

But, before we proceed, we need to confirm that billing for storage is enabled. Otherwise, Google won’t allow us to create one.

Note: Follow the instructions in this lesson to fulfill the billing requirement.

Viewing storage.tf#

We’re about to explore the google_storage_bucket module. It allows us to manage Google Cloud storage buckets. More information can be found in the google_storage_bucket documentation.

Let’s take a look at the definition.

Output of storage.tf
  • We’re defining storage bucket referenced as state. All resource entries are followed with a type (e.g., google_storage_bucket) and a reference (e.g., state). We’ll see the usage of a reference later in one of the upcoming definitions.

  • Just like with the provider, the resource has several fields. Some are mandatory, while others are optional and often have predefined values.

  • We’re defining the name and the location. Later on, we specified that it should be created inside our project.

  • Finally, we selected NEARLINE as the storage class. Please visit the Available storage classes section of the documentation to see the full list.

Just like before, the values of some of those fields are defined as variables. Others (those less likely to change) are hard-coded.

The labels are there to provide our team members or other people in the organization metadata about our cluster. If we’ve forgotten about it for months, it’s easy to tell who to bother. We also state that we manage the cluster with Terraform, which will hopefully prevent people from making manual changes through the UI.

There’s one tiny problem we need to fix before we proceed.

Google Cloud bucket names need to be globally unique. There can’t be two buckets with the same name anywhere in Google Cloud, among all its users. We’ll generate a unique name using date.

The name of the bucket is now, more or less, unique, and we won’t face the danger that someone else has already claimed it. The environment variable we created will be used as the name of the bucket.

Google Cloud bucket

The name of the bucket is also now, more or less, unique, and we won’t face the danger of someone else claiming it. The environment variable we created will be used as the name of the bucket.

Let’s apply the new definition.

The output, limited to the relevant parts, is as follows.

Output of terraform apply

In this case, we can see the full list of all the resources that will be created. The “+” sign indicates that something will be created. Under different conditions, we could also observe those that would be modified (“∼”) or destroyed ("-").

Applying Terraform#

Here Terraform will deduce that the actual state is missing the google_storage_bucket resource. It also shows us what properties will be used to create that resource. We defined some, while others will become known after we apply that definition.

Devops
Devops
Google storage bucket
Google storage b...
Terraform Apply
Terraform Apply
Push
Push
Pull
Pull
Stores
Stores
*.tfstate
*.tfstate
Viewer does not support full SVG 1.1
Overview of devops interacting with storage bucket

Finally, we’re asked whether we want to perform these actions. We should type “Yes” and press the “Enter” key.

Note: From here on out, we won’t explicitly explain that we need to confirm Terraform actions.

After we choose to proceed, the relevant parts of the output should be as follows.

Output of terraform apply after successful execution

We can see that one resource was added and that nothing was changed or destroyed.

Since this is the first time we’ve created a resource with Terraform, it’s reasonable to be skeptical that everything worked perfectly. So, we’ll confirm that the bucket was indeed created by listing all of those available in the project. Over time, we’ll gain confidence in Terraform and won’t have to validate that everything works correctly.

Let’s imagine that someone else executed terraform apply and that we’re not sure what the state of the resources is. In such a situation, we can consult Terraform by asking it to show us the state.

The output is as follows.

Output of terraform show

There’s not much to look at. For now, we have only one resource (google_storage_bucket). As we keep progressing, that output will increase and, more importantly, it will always reflect the state of the resources managed by Terraform.

The previous output is a human-readable format of the state currently stored in terraform.tfstate. We can also inspect that file by using cat terraform.tfstate in the code playground below.

The output is as follows.

Output of terraform.tfstate

If we ignore the fields that are currently empty, and the few that are for Terraform’s internal usage, we can see that the state stored in that file contains the same information as what we saw through terraform show. The only important difference is that one is in Terraform’s internal format (terraform.tfstate), while the other (terraform show) is meant to be readable by humans.

Even though it’s not the case right now, the state could easily contain confidential information. It’s currently stored locally, and we’ve already decided to move it to Google Cloud bucket. That way, we’ll be able to share it, it will be stored in a more reliable location, and it will be more secure.

Moving the state to the bucket#

To move the state to the bucket, we’ll create a gcs (Google Cloud Storage) backend. We’ve already prepared a file backend.tf just for that. The definition of backend.tf is shown below. To see the file in the code playground below, use catbackend.tf.

Viewing backend.tf#

Output of backend.tf

There’s nothing special in that definition. We’re setting the name of the bucket, the prefix, which will be appended to the files, and the path to the credentials file.

The bucket entry in that Terraform definition cannot be set to a value of a variable. It needs to be hard-coded. So, we’ll need to replace devops-catalog with the bucket name we used when we created it.

Replacing the bucket name

Let’s apply the definitions and see what we’ll get.

The output, limited to the relevant parts, is as follows.

Output of terraform apply

Since we’re changing the location where Terraform should store the state, we have to initialize the project again. The last time we did that, it was because a plugin (Google) was missing. This time it’s because the init process will copy the state from the local file to the newly created bucket.

The output, limited to the relevant parts, is as follows.

Output of terraform init

Confirm copying the state by typing “Yes” and pressing the “Enter” key.

The process continues. It copies the state to the remote storage, which, from now on, will be used instead of the local file. Now we should be able to apply the definitions. The output is as follows.

Output of terraform apply

As we can see, there was no need to apply the definitions. The latest addition does not define any new resources. We only added the location for the Terraform state. That change is internal, and it was applied through the init process.

Try it yourself#

You can try all of the commands used in this lesson in the code playground below. Press the run button and wait for a few seconds for it to connect.

Before running the commands used in this section, you first need to connect to GCloud to create the secret keys for the service account. For ease of use, all of the commands above are combined in main.sh.

Please provide values for the following:
type
Not Specified...
project_id
Not Specified...
private_key_id
Not Specified...
private_key
Not Specified...
client_email
Not Specified...
client_id
Not Specified...
auth_uri
Not Specified...
token_uri
Not Specified...
auth_provider_x509_cert_url
Not Specified...
client_x509_cert_url
Not Specified...
GCP_TF_VAR_state_bucket
Not Specified...
GCP_PROJECT_ID
Not Specified...
TF_VAR_project_id
Not Specified...
/
main.sh
provider.tf
storage.tf
variables.tf
terraform.tfstate
account.json
files
Try it yourself

Terraform Providers

Creating the Control Plane